package com.example.proxy.dynamic.myjdk.core;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class MyProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(
            MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler h) {

        try {
            String src = generateSrc(interfaces);
            System.out.println(src);

            //2、Java文件输出磁盘
            String filePath = MyProxy.class.getResource("").getPath();
            File f = new File(filePath + "$Proxy0.java");
            System.out.println("$Proxy0.java:" + f.getPath());
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            //3、把生成的.java文件编译成.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();

            //4、编译生成的.class文件加载到JVM中来
            Class proxyClass = classLoader.findClass("$Proxy0");

            // 因为构造函数可能会有很多参数，所以这里定义获取只有一个参数的构造器
            // 并且这个参数的实际类型必须是实现了 MyInvocationHandler 的子类
            Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
            f.delete();

            // 这里才是创建真正的对象
            //5、返回字节码重组以后的新的代理对象
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {
        Class clazz = MyProxy.class;
        Class<?> anInterface = interfaces[0];

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(clazz.getPackage().getName()).append(";").append(ln);
        sb.append("import ").append(anInterface.getName()).append(";").append(ln);
        sb.append("import java.lang.reflect.*;").append(ln);
        sb.append("public class $Proxy0 implements ").append(anInterface.getSimpleName()).append("{").append(ln);
        sb.append("public MyInvocationHandler h;").append(ln);
        sb.append("public $Proxy0(MyInvocationHandler h){").append(ln);
        sb.append("this.h = h;").append(ln);
        sb.append("}").append(ln);

        Method[] methods = anInterface.getMethods();
        for (Method method : methods) {
            String returnType = method.getReturnType().getName();
            String methodName = method.getName();
            Parameter[] parameters = method.getParameters();

            StringBuilder paramNames = new StringBuilder();
            StringBuilder paramsClasses = new StringBuilder();
            StringBuilder paramsValues = new StringBuilder();
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                String paramClass = parameter.getType().getSimpleName();
                String paramName = parameter.getName();
                paramNames.append(paramClass).append(" ").append(paramName);
                paramsClasses.append(paramClass).append(".class");
                paramsValues.append(paramName);

                if (i < parameters.length - 1) {
                    paramNames.append(",");
                    paramsClasses.append(",");
                    paramsValues.append(",");
                }

            }

            sb.append("public ").append(returnType).append(" ").append(methodName).append("(").append(paramNames).append("){").append(ln);
            sb.append("try {").append(ln);
            sb.append("Method m = ").append(anInterface.getSimpleName()).append(".class.getMethod(\"").append(methodName).append("\",new Class[]{").append(paramsClasses).append("});").append(ln);
            sb.append(hasReturnValue(method.getReturnType()) ? "return (" + method.getReturnType().getName() + ")" : "");
            sb.append("this.h.invoke(this,m,new Object[]{").append(paramsValues).append("})").append(";").append(ln);
            sb.append("} catch(Throwable e){").append(ln);
            sb.append("throw new UndeclaredThrowableException(e);" + ln);
            sb.append("}").append(ln);
            sb.append("}").append(ln);
        }

        sb.append("}").append(ln);
        return sb.toString();
    }

    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}
